package nth.bananas.game;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;

import nth.bananas.solving.Letter;

import org.apache.commons.lang.ArrayUtils;

public class GameMaster {

	private int players; // how many are playing the game?
	private List<Letter> bunch = new ArrayList<Letter>(144); // list for random access. max 144
	private Queue<Player> peels = new LinkedList<Player>();
	Random rando = new Random();


	public GameMaster(int players, PieceCounts counts) {
		if (players < 1) {
			throw new IllegalArgumentException(String.format("Must have at least one player; %d specified.", players));
		}
		this.players = players;

		for (Entry<Letter, Integer> e : counts.getCounts().entrySet()) {
			for (int i = 0; i < e.getValue(); i++) {
				bunch.add(e.getKey());
			}
		}
	}

	/**
	 * Checks if it is Banana time.
	 * This is true if there are fewer pieces in the bunch than there are players.
	 * (Another peel cannot happen.) 
	 * @return	true if the game is in Banana mode
	 */
	public boolean isBananas() {
		return bunch.size() < players;
	}

	/**
	 * Chooses a random tile, removes it from the bunch, and returns it.
	 * 
	 * @return	Letter from the bunch
	 */
	public Letter emitRandom() {
		if (! bunch.isEmpty()) {
			int randomIndex = rando.nextInt(bunch.size());
			Letter result = bunch.get(randomIndex);
			bunch.remove(randomIndex);
			return result;
		}
		return null;
	}

	/**
	 * Gets the initial tiles per player, given the number of players in this game.
	 * <pre>
	 * 1-4 players	21 letters
	 * 5-6 players	15 letters
	 * 7+ players	11 letters
	 * </pre>
	 */
	public int tilePerPlayer() {
		if (players >= 1 && players <= 4) {
			return 21;
		}
		if (players >= 5 && players <= 6) {
			return 15;
		}
		return 11;
	}

	/**
	 * Adds an event to the queue.
	 * @param gameEvent
	 */
	public void postPeel(Player p) {
		peels.add(p);
	}

	public void takeTile(Letter tile) {
		bunch.add(tile);
	}

	protected int bunchSize() {
		return bunch.size();
	}

	private Set<Player> processPeelRequests(Player[] players) {
		Set<Player> winners = new HashSet<Player>();
		
		Player p = peels.poll();
		while (p != null) {
		
			if (isBananas()) {
				winners.add(p);
			}
			else {
				for (Player gamer : players) {
					gamer.peel();
				}
			}
			
			p = peels.poll();
		}
		
		for (Player w : winners) {
			assert ArrayUtils.contains(players, w) : String.format("%s is found to be a winner, but is not in the game.", w);
		}
	
		return winners.size() == 0 ? null : winners;
	}

	public Set<Player> playGame(Player[] players) {
		if (Game.verbose) {
			System.out.printf("Playing round %d%n", Game.roundsPlayed++);
		}
		
		Set<Player> winners = processPeelRequests(players);
	
		if (winners == null) {
			for (Player p : players) {
				p.work();
			}
		}
	
		return winners;
	}

	// probably should be protected. who needs to see this other than players?
	public boolean canTakeDump() {
		return bunch.size() > 2;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((bunch == null) ? 0 : bunch.hashCode());
		result = prime * result + players;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		GameMaster other = (GameMaster) obj;
		if (bunch == null) {
			if (other.bunch != null)
				return false;
		} else if (!bunch.equals(other.bunch))
			return false;
		if (players != other.players)
			return false;
		if (rando == null) {
			if (other.rando != null)
				return false;
		} else if (!rando.equals(other.rando))
			return false;
		return true;
	}

}
